package org.geektimes.web.mvc;

import org.apache.commons.lang.StringUtils;
import org.geektimes.web.mvc.context.ComponentContext;
import org.geektimes.web.mvc.controller.Controller;
import org.geektimes.web.mvc.controller.PageController;
import org.geektimes.web.mvc.controller.RestController;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

import static java.util.Arrays.asList;
import static org.apache.commons.lang.StringUtils.substringAfter;

public class FrontControllerServlet extends HttpServlet {

    /**
     * 请求路径和 Controller 的映射关系缓存
     */
    private Map<String, Controller> controllersMapping = new HashMap<>();

    /**
     * 请求路径和 {@link HandlerMethodInfo} 映射关系缓存
     */
    private Map<String, HandlerMethodInfo> handleMethodInfoMapping = new HashMap<>();

    /**
     * 初始化 Servlet
     *
     * @param servletConfig
     */
    @Override
    public void init(ServletConfig servletConfig) {
        initComponentContext(servletConfig.getServletContext());

        initHandlerMethods();

//        initHandleMethods();
    }

    @Override
    public void destroy() {
        ComponentContext.getInstance().destroy();
    }

    private void initHandlerMethods() {
        List<Object> controllerComponentList =
                ComponentContext.getInstance().getComponent(PageController.class, RestController.class);

        controllerComponentList.forEach(controller -> {
            Class<?> controllerClass = controller.getClass();

            Path typePath = controllerClass.getAnnotation(Path.class);
            String rootPath = Objects.isNull(typePath) ? "" : typePath.value();

            Method[] publicMethods = controllerClass.getMethods();
            for (Method publicMethod : publicMethods) {
                Set<String> supportedHttpMethods = findSupportedHttpMethods(publicMethod);
                Path methodPath = publicMethod.getAnnotation(Path.class);

                String requestPath = rootPath;
                if (Objects.nonNull(methodPath)){
                    requestPath +=  ("/" + methodPath.value());
                }

                if (StringUtils.isNotBlank(requestPath)){
                    requestPath = requestPath.replaceAll("\\/+", "/");

                    handleMethodInfoMapping.put(requestPath, new HandlerMethodInfo(requestPath, publicMethod, supportedHttpMethods));
                    controllersMapping.put(requestPath, (Controller) controller);
                }
            }
        });
    }

    private void initComponentContext(ServletContext servletContext) {
        ComponentContext context = new ComponentContext();
        context.init(servletContext);
    }

    /**
     * 读取所有的 RestController 的注解元信息 @Path
     * 利用 ServiceLoader 技术（Java SPI）
     */
    private void initHandleMethods() {
        for (Controller controller : ServiceLoader.load(Controller.class)) {
            Class<?> controllerClass = controller.getClass();

            Path pathFromClass = controllerClass.getAnnotation(Path.class);
            String requestPath = Objects.isNull(pathFromClass) ? "" : pathFromClass.value();

            Method[] publicMethods = controllerClass.getMethods();
            // 处理方法支持的 HTTP 方法集合
            for (Method method : publicMethods) {
                Set<String> supportedHttpMethods = findSupportedHttpMethods(method);
                Path pathFromMethod = method.getAnnotation(Path.class);

                String methodPath = requestPath;
                if (pathFromMethod != null) {
                    methodPath += pathFromMethod.value();
                }
                handleMethodInfoMapping.put(methodPath,
                        new HandlerMethodInfo(requestPath, method, supportedHttpMethods));
                controllersMapping.put(methodPath, controller);
            }
        }
    }

    /**
     * 获取处理方法中标注的 HTTP方法集合
     *
     * @param method 处理方法
     * @return
     */
    private Set<String> findSupportedHttpMethods(Method method) {
        Set<String> supportedHttpMethods = new LinkedHashSet<>();
        for (Annotation annotationFromMethod : method.getAnnotations()) {
            HttpMethod httpMethod = annotationFromMethod.annotationType().getAnnotation(HttpMethod.class);
            if (httpMethod != null) {
                supportedHttpMethods.add(httpMethod.value());
            }
        }

        if (supportedHttpMethods.isEmpty()) {
            supportedHttpMethods.addAll(asList(HttpMethod.GET, HttpMethod.POST,
                    HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.HEAD, HttpMethod.OPTIONS));
        }

        return supportedHttpMethods;
    }

    /**
     * SCWCD
     *
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    @Override
    public void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 建立映射关系
        // requestURI = /a/hello/world
        String requestURI = request.getRequestURI();
        // contextPath  = /a or "/" or ""
        String servletContextPath = request.getContextPath();
        String prefixPath = servletContextPath;
        // 映射路径（子路径）
        String requestMappingPath = substringAfter(requestURI,
                StringUtils.replace(prefixPath, "//", "/"));
        // 映射到 Controller
        Controller controller = controllersMapping.get(requestMappingPath);

        if (controller != null) {
            HandlerMethodInfo handlerMethodInfo = handleMethodInfoMapping.get(requestMappingPath);

            try {
                if (handlerMethodInfo != null) {

                    String httpMethod = request.getMethod();

                    if (!handlerMethodInfo.getSupportedHttpMethods().contains(httpMethod)) {
                        // HTTP 方法不支持
                        response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                        return;
                    }

                    // 形参 处理
                    Method handlerMethod = handlerMethodInfo.getHandlerMethod();
                    Parameter[] methodParameters = handlerMethod.getParameters();

                    // 实参 处理
                    Map<String, String[]> parameterMap = request.getParameterMap();

                    // 参数封装
                    Object[] paramValues = new Object[handlerMethod.getParameterCount()];
                    for (int i = 0; i < methodParameters.length; i++) {
                        Class<?> methodParameterType = methodParameters[i].getType();
                        if (methodParameterType == HttpServletRequest.class){
                            paramValues[i] = request;
                            continue;
                        }
                        if (methodParameterType == HttpServletResponse.class){
                            paramValues[i] = response;
                            continue;
                        }

                        if (methodParameterType == Integer.class){
                            String parameterValue = parameterValueConvertToString(parameterMap.get(methodParameters[i].getName()));
                            paramValues[i] = Integer.valueOf(parameterValue);
                            continue;
                        }
                        if (methodParameterType == String.class){
                            paramValues[i] = parameterValueConvertToString(parameterMap.get(methodParameters[i].getName()));
                            continue;
                        }
                        if (methodParameterType == Long.class){
                            String parameterValue = parameterValueConvertToString(parameterMap.get(methodParameters[i].getName()));
                            paramValues[i] = Long.valueOf(parameterValue);
                            continue;
                        }
                        if (methodParameterType == Double.class){
                            String parameterValue = parameterValueConvertToString(parameterMap.get(methodParameters[i].getName()));
                            paramValues[i] = Double.valueOf(parameterValue);
                            continue;
                        }

                        /**
                         * 对象封装 ： 只能封装基本数据类型，对象内包含对象 以后优化
                         */
                        Object instance = methodParameterType.newInstance();
                        BeanInfo beanInfo = Introspector.getBeanInfo(methodParameterType);
                        PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
                        for (PropertyDescriptor descriptor : descriptors) {
                            String name = descriptor.getName();
                            if (parameterMap.containsKey(name)){
                                String parameterValue = parameterValueConvertToString(parameterMap.get(name));

                                Class<?> propertyType = descriptor.getPropertyType();
                                Object value = parameterValue;
                                if (propertyType == Integer.class){
                                    value = Integer.valueOf(parameterValue);
                                }
                                if (propertyType == Long.class){
                                    value = Long.valueOf(parameterValue);
                                }
                                if (propertyType == Double.class){
                                    value = Double.valueOf(parameterValue);
                                }

                                Method writeMethod = descriptor.getWriteMethod();

                                writeMethod.invoke(instance, value);
                            }
                        }
                        paramValues[i] = instance;
                    }

                    if (controller instanceof PageController) {
                        String viewPath = (String) handlerMethod.invoke(controller, paramValues);

                        // 页面请求 forward
                        // request -> RequestDispatcher forward
                        // RequestDispatcher requestDispatcher = request.getRequestDispatcher(viewPath);
                        // ServletContext -> RequestDispatcher forward
                        // ServletContext -> RequestDispatcher 必须以 "/" 开头
                        ServletContext servletContext = request.getServletContext();
                        if (!viewPath.startsWith("/")) {
                            viewPath = "/" + viewPath;
                        }
                        RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher(viewPath);
                        requestDispatcher.forward(request, response);
                    }
                    else if (controller instanceof RestController) {
                        Object result = handlerMethod.invoke(controller, paramValues);

                        response.setContentType("text/plain");
                        response.getWriter().write(result.toString());
                    }
                }
            } catch (Throwable throwable) {
                if (throwable.getCause() instanceof IOException) {
                    throw (IOException) throwable.getCause();
                } else {
                    throw new ServletException(throwable.getCause());
                }
            }
        }
    }

    private String parameterValueConvertToString(String[] values) {
        return Arrays.toString(values)
                .replaceAll("\\[|\\]", "")
                .replaceAll("\\s", "");
    }
}
